Scroll to navigation

EXECVE(2) Руководство программиста Linux EXECVE(2)

ИМЯ

execve - выполнить программу

ОБЗОР

#include <unistd.h>

int execve(const char *filename, char *const argv[],
char *const envp[]);

ОПИСАНИЕ

execve() выполняет программу, задаваемую аргументом filename. В filename должно быть указано имя двоичного исполняемого файла или сценарий, начинающийся со строки вида:


#! интерпретатор [необязательные параметры]

Подробней о сценариях написано далее в "Интерпретируемые сценарии".

argv — это массив строковых параметров, передаваемых новой программе. По соглашению, в первой строке должно содержаться имя файла, относящееся к запускаемой программе. envp — это массив строк в формате ключ=значение, которые передаются новой программе в качестве окружения (environment). Оба массива argv и envp завершаются указателем NULL. К массиву параметров и окружению можно обратиться из вызываемой программой функции main, если она определена как:


int main(int argc, char *argv[], char *envp[])

При успешном выполнении execve() управление не возвращается, а код, данные, bss и стек вызвавшего процесса перезаписываются загруженной программой.

Если текущая программа выполнялась под управлением ptrace, то после успешного вызова execve() ей посылается сигнал SIGTRAP.

Если у файла программы, указанного в filename, установлен бит set-user-ID и файловая система, в которой он хранится, не смонтирована с параметром nosuid (флаг MS_NOSUID у mount(2)), и вызывающий процесс не выполняется под управлением ptrace, то фактический идентификатор пользователя вызывающего процесса меняется на идентификатор владельца файла программы. Точно также, если на файле программы установлен бит set-group-ID, то фактический идентификатор группы вызывающего процесса становится равным группе, которой принадлежит файл программы.

Фактический идентификатор пользователя процесса копируется в сохранённый идентификатор пользователя (set-user-ID), также фактический идентификатор группы копируется в сохранённый идентификатор группы (set-group-ID). Это копирование выполняется после изменения любого фактического идентификатора, которое происходит из-за выставленных бит прав set-user-ID и set-group-ID.

Если исполняемый файл является динамически-скомпонованным файлом в формате a.out, содержащим заглушки для динамических библиотек, то в начале выполнения этого файла вызывается динамический компоновщик Linux — ld.so(8), который загружает библиотеки в память и компонует их с исполняемым файлом.

Если исполняемый файл является динамически-скомпонованным файлом в формате ELF, то для загрузки необходимых динамических библиотек используется интерпретатор, указанный в сегменте PT_INTERP. Обычно, это /lib/ld-linux.so.2 для программ, скомпонованных с glibc 2. (для программ, скомпонованных со старой Linux libc5, обычно это /lib/ld-linux.so.1).

При вызове execve() сохраняются все свойства процесса, за исключением:

  • Значения обработчиков всех захватываемых сигналов сбрасываются в значения по умолчанию (signal(7)).
  • Любой альтернативный стек сигнала не сохраняется (sigaltstack(2)).
  • Проецирование памяти не сохраняется (mmap(2)).
  • Подключённые общие сегменты памяти System V отключаются (shmat(2)).
  • Области общей памяти POSIX становятся неспроецированными (shm_open(3)).
  • Открытые дескрипторы в очереди сообщений POSIX закрываются (mq_overview(7)).
  • Все открытые именные семафоры POSIX закрываются (sem_overview(7)).
  • Таймеры POSIX не сохраняются (timer_create(2)).
  • Все открытые потоки каталогов (directory streams) закрываются (opendir(3)).
  • Блокировки памяти не сохраняются (mlock(2), mlockall(2)).
  • Обработчики завершения работы (exit handlers) не сохраняются (atexit(3), on_exit(3)).
  • Окружения плавающей точки сбрасываются в настройки по умолчанию (fenv(3)).

В POSIX.1-2001 определён список сохраняемых свойств процесса. Следующие свойства процесса, имеющиеся только в Linux, также не сохраняются при execve():

  • Устанавливается флаг PR_SET_DUMPABLE (prctl(2)), если выполняемая программа не имеет установленных бит set-user-ID или set-group-ID; в противном случае он очищается.
  • Флаг PR_SET_KEEPCAPS (prctl(2)) очищается.
  • Имя процесса, установленное через prctl(2) PR_SET_NAME (и отображаемое ps -o comm), изменяется на имя нового исполняемого файла.
  • Сигнал завершения (termination signal) устанавливается в SIGCHLD (clone(2)).

Также стоит учитывать следующее:

  • Все нити (threads), отличные от вызывающей, уничтожаются execve(). Мьютексы, условные переменные и другие объекты pthreads не сохраняются.
  • При запуске программы выполняется эквивалент setlocale(LC_ALL, "C").
  • В POSIX.1-2001 указано, что действия по отношению к любым игнорируемым или имеющим настройку по умолчанию сигналам, остаются неизменными. В POSIX.1-2001 есть одно исключение: если SIGCHLD игнорируется, то реализация может оставить обработку сигнала (disposition) неизменной или вернуть настройку по умолчанию; в Linux используется первое.
  • Все ожидающие выполнения асинхронные операции ввода-вывод отменяются (aio_read(3), aio_write(3)).
  • Как происходит обработка мандатов (capabilities) при вызове execve(), см. capabilities(7).
  • По умолчанию, файловые дескрипторы остаются открытыми после execve(). Файловые дескрипторы, помеченные как close-on-exec (закрывать при запуске), закрываются; см. описание FD_CLOEXEC в fcntl(2). (Если файловый дескриптор закрыт, это приводит к освобождению всех имеющихся блокировок, полученных на соответствующий файл данным процессом. Подробней см. fcntl(2).) В POSIX.1-2001 сказано, что если бы файловые дескрипторы 0, 1 и 2 были закрыты после успешного вызова execve(), и процесс получил бы привилегии из-за установленных битов set-user_ID или set-group_ID на исполняемом файле, то система смогла бы открыть произвольный файл для каждого из этих дескрипторов. Считается, что переносимая программа, с привилегиями или без, не может рассчитывать, что эти три файловых дескриптора будут оставаться закрытыми после execve().

Интерпретируемые сценарии

Интерпретируемый сценарий — это текстовый файл, у которого установлен бит выполнения и первая строка имеет вид:


#! интерпретатор [необязательные параметры]

В поле интерпретатор должно быть указано имя файла запуска, это не имя самого файла сценария. Если в аргументе filename для execve() указан интерпретируемый сценарий, то интерпретатор будет вызван со следующими параметрами:


интерпретатор [необязательный параметр] имя файла параметр...

где параметр... — последовательность слов, указываемых аргументом argv в execve().

В целях переносимости, необязательный параметр должен быть или пустым, или задаваться одним словом (т.е., не должен содержать пробельных символов); см. ЗАМЕЧАНИЯ далее.

Ограничения на размер параметров и окружения

Большинство реализаций UNIX накладывает некоторые ограничения на полный размер параметра командной строки (argv) и окружения (envp), которые можно передать новой программе. POSIX.1 позволяет реализации объявить это ограничение через константу ARG_MAX (определённую в <limits.h> или сделать её доступной во время выполнения через вызов sysconf(_SC_ARG_MAX)).

В ядре Linux до версии 2.6.23 размер памяти, используемый для хранения окружения и строк параметров, был ограничен 32 страницами (определялся ядерной константой MAX_ARG_PAGES). На архитектурах с 4-КиБ размером страницы это давало максимальный размер в 128 КиБ.

Начиная с ядра версии 2.6.23, большинство архитектур поддерживают предельный размер, высчитываемый от мягкого ограничения ресурса RLIMIT_STACK (см. getrlimit(2)), который действует во время вызова execve(). (Исключение составляют архитектуры без механизма управления памятью: в них ограничение рассчитывается как и до версии 2.6.23.) Это изменение позволяет программам иметь больший список параметров и/или окружения. Для этих архитектур полный размер ограничен до 1/4 разрешённого размера стека. (Накладываемое ограничение в 1/4 позволяет новой программе всегда иметь некоторое пространство под стек.) Начиная с Linux версии 2.6.25, ядро отводит нижние 32 страницы для этого предельного размера, поэтому, даже когда RLIMIT_STACK задан слишком низко, приложения гарантированно получат, по крайней мере, столько же пространства под параметры и окружение, сколько бы они получили при работе с Linux 2.6.23 и ранее. (Это гарантия не обеспечивалась в Linux 2.6.23 и 2.6.24.) Также, размер строки ограничен 32 страницами (ядерная константа MAX_ARG_STRLEN), а максимальное число строк может быть 0x7FFFFFFF.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

При успешном выполнении execve() не возвращает управление. В случае ошибки возвращается -1, а errno устанавливается в соответствующее значение.

ОШИБКИ

Слишком большое общее количество байт для окружения (envp) и списка параметров (argv).
В одном из каталогов префикса filename или интерпретатора не разрешён поиск. (см. также path_resolution(7))
Файл или интерпретатор не являются обычным файлом.
Не установлен бит выполнения на файле или сценарии или интерпретаторе ELF.
Файловая система смонтирована с noexec.
Аргумент filename указывает за пределы доступного адресного пространства.
Исполняемый ELF-файл содержит более одного сегмента PT_INTERP (т.е., в нём указано более одного интерпретатора).
Произошла ошибка ввода-вывода.
Интерпретатор ELF является каталогом.
Не распознан формат интерпретатора ELF.
Во время определения filename, имени сценария или интерпретатора ELF встретилось слишком много символьных ссылок.
Было достигнуто ограничение по открытым файловым дескрипторам на процесс.
Слишком длинное значение аргумента filename.
Достигнуто максимальное количество открытых файлов в системе.
Файл filename, сценарий или интерпретатор ELF не существует, или не найдена динамическая библиотека, необходимая для файлового интерпретатора.
Не распознан формат исполняемого файла, он не подходит для архитектуры, или имеет ошибки в формате, из-за чего не может быть выполнен.
Недостаточное количество памяти ядра.
Компонент пути в filename, сценарии или интерпретаторе ELF в действительности не является каталогом.
Файловая система смонтирована с nosuid, пользователь не имеет прав суперпользователя, а у файла установлен бит set-user-ID или set-group-ID.
Над процессом выполняется трассировка, пользователь не имеет прав суперпользователя, а у файла установлен бит set-user-ID или set-group-ID.
Исполняемый файл был открыт на запись одним или более процессов.

СООТВЕТСТВИЕ СТАНДАРТАМ

SVr4, 4.3BSD, POSIX.1-2001. В POSIX.1-2001 не описано поведение #!, но в остальном совместимость есть.

ЗАМЕЧАНИЯ

Над процессами с установленными set-user-ID и set-group-ID не может выполняться ptrace(2).

В Linux игнорируются биты set-user-ID и set-group-ID на файлах со сценариями.

Результат работы при монтировании файловой системы с параметром nosuid различается в разных версиях ядра Linux: некоторые будут отказывать в запуске исполняемых файлов с установленными битами set-user-ID и set-group-ID, если это дало бы пользователю больше прав чем уже есть (и возвращать EPERM), другие просто проигнорируют биты set-user-ID и set-group-ID и успешно выполнят exec().

При указании интерпретатора сценариев в #! максимальная длина строки равна 127 символов.

Семантика необязательного параметра интерпретатора сценариев различна в разных реализациях. В Linux, вся строка после имени интерпретатора передаётся интерпретатору как единый параметр, и эта строка может содержать пробельные символы. Однако, такое поведение отличается от других систем. Некоторые системы используют первый пробел в качестве признака окончания необязательного параметра. В других системах, интерпретатор сценариев может иметь несколько параметров, и пробелы в необязательном параметре используются для их разграничения.

В Linux аргумент argv может быть задан значением NULL, что приводит к тому же результату, как если бы этот аргумент указывал бы на список, содержащий единственный указатель на NULL. Не используйте мнимые преимущества данного свойства! Это нестандартное поведение и не переносимо: в большинстве других систем UNIX это приводит к ошибке (EFAULT).

В POSIX.1-2001 указано, что значения, возвращаемые sysconf(3), должны быть неизменны в течении существования процесса. Однако, начиная с версии Linux 2.6.23, если изменяется ограничение ресурса RLIMIT_STACK, то значение, возвращаемое для _SC_ARG_MAX, также будет изменено, чтобы отразить, что ограничение на пространство для хранения параметров командной строки и окружения было изменено.

Историческая справка

В UNIX V6 список аргументов вызова exec() заканчивался 0, а список аргументов main заканчивался -1. Поэтому, этот список аргументов не мог быть использован напрямую в последующем вызове exec(). Начиная с UNIX V7 оба списка стали оканчиваться NULL.

ПРИМЕР

Данная программа запускается второй программой, представленной ниже. Она просто выводит свои параметры командной строки по одному на строку.


/* myecho.c */
#include <stdio.h>
#include <stdlib.h>
int
main(int argc, char *argv[])
{

int j;
for (j = 0; j < argc; j++)
printf("argv[%d]: %s\n", j, argv[j]);
exit(EXIT_SUCCESS); }

Эта программа может использоваться для запуска программы, чьё имя указано в параметре командной строки.

/* execve.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{

char *newargv[] = { NULL, "hello", "world", NULL };
char *newenviron[] = { NULL };
if (argc != 2) { fprintf(stderr, "Использование: %s <запускаемый_файл>\n", argv[0]); exit(EXIT_FAILURE);
}
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
perror("execve"); /* execve() возвращается только при ошибке */
exit(EXIT_FAILURE); }

Мы можем использовать вторую программу для запуска первой:


$ cc myecho.c -o myecho
$ cc execve.c -o execve
$ ./execve ./myecho
argv[0]: ./myecho
argv[1]: hello
argv[2]: world

Также мы можем использовать эти программы для демонстрации использования интерпретатора сценариев. Для этого создадим сценарий, чей "интерпретатор" указывает на нашу программу myecho:


$ cat > script.sh
#! ./myecho script-arg
^D
$ chmod +x script.sh

Теперь мы можем использовать нашу программу для запуска сценария:


$ ./execve ./script.sh
argv[0]: ./myecho
argv[1]: script-arg
argv[2]: ./script.sh
argv[3]: hello
argv[4]: world

СМОТРИТЕ ТАКЖЕ

chmod(2), fork(2), ptrace(2), execl(3), fexecve(3), getopt(3), credentials(7), environ(7), path_resolution(7), ld.so(8)

2012-05-04 Linux